package com.ea.eadp.nexus.proxy.tests.risk;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

import org.testng.Assert;
import org.testng.annotations.AfterClass;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

import com.ea.eadp.nexus.test.common.AccessToken;
import com.ea.eadp.nexus.test.common.NexusGatewayApiEnum;
import com.ea.eadp.nexus.test.common.TestUtil;
import com.ea.eadp.nexus.test.common.ApiClients.NexusConnectApi;
import com.ea.eadp.nexus.test.common.ApiClients.NexusGatewayApi;
import com.ea.eadp.rest.risk.generated.EnumRefTableColumnType;
import com.ea.eadp.rest.risk.generated.EnumRefTableKeyType;
import com.ea.eadp.rest.risk.generated.EnumRefTableServiceProviderType;
import com.ea.eadp.rest.risk.generated.RefTableColumnDefinition;
import com.ea.eadp.rest.risk.generated.RefTableConfig;
import com.ea.eadp.rest.risk.generated.RefTableConfig.Columns;
import com.ea.eadp.rest.risk.generated.RefTableDefinition;
import com.ea.eadp.rest.risk.generated.RefTableItem;
import com.ea.eadp.rest.risk.generated.RefTableItem.Values;
import com.ea.eadp.rest.risk.generated.RefTableItemArray;
import com.ea.eadp.test.shared.client.risk.RiskClient;
import com.ea.nucleus.caseproperties.CaseProperties;
import com.ea.nucleus.caseproperties.Component;
import com.ea.nucleus.caseproperties.Priority;
import com.ea.nucleus.caseproperties.Release;
import com.ea.nucleus.caseproperties.Status;
import com.ea.nucleus.caseproperties.ReleaseFeatures.Mar2015;
import com.ea.nucleus.shared.http.search.PaginationUtils;

@CaseProperties(
        release = Release.Mar2015,
        component = Component.Risk,
        features = {Mar2015.GOPFR_952},
        owner = "Marvin"
)
public class ManipulateRefTableContents {
    // risk.reson.reftable.cache.max.allowed.rows
    private static final Integer MAX_CACHE_SIZE = 10000;
    private static final String CLIENT_ID = "fraud-ops";
    private static final String CLIENT_SECRET = "fraud-ops-secret";
    private static final String SCOPE = "basic.reson";

    private static RiskClient riskClient = null;

    private HashMap<String,String> context = null;
    private String refTableName = null;

    @BeforeClass(alwaysRun = true)
    public static void createRiskClient() throws Exception {
        riskClient = new RiskClient();
    }

    @AfterClass(alwaysRun = true)
    public static void destroyRiskClient() throws Exception {
        riskClient = null;
    }

    @BeforeMethod(alwaysRun = true)
    public final void initContext() throws Exception {
        RefTableColumnDefinition keyColumn = new RefTableColumnDefinition();
        keyColumn.setName(UUID.randomUUID().toString());
        keyColumn.setType(EnumRefTableColumnType.STRING);
        keyColumn.setNullable(false);
        keyColumn.setOrder(1);

        RefTableColumnDefinition valueColumn = new RefTableColumnDefinition();
        valueColumn.setName(UUID.randomUUID().toString());
        valueColumn.setType(EnumRefTableColumnType.STRING);
        valueColumn.setNullable(false);
        valueColumn.setOrder(2);

        Columns refTableColumns = new Columns();
        refTableColumns.getColumn().add(keyColumn);
        refTableColumns.getColumn().add(valueColumn);

        RefTableConfig refTableConfig = new RefTableConfig();
        refTableConfig.setRefTableKeyType(EnumRefTableKeyType.SINGLE);
        refTableConfig.setServiceProviderType(EnumRefTableServiceProviderType.REF_TABLE_GENERIC);
        refTableConfig.setColumns(refTableColumns);

        RefTableDefinition refTable = new RefTableDefinition();
        refTable.setName(UUID.randomUUID().toString());
        refTable.setDescription(UUID.randomUUID().toString());
        refTable.setType("REFTABLE");
        refTable.setUpdateBy("integration-tests");
        refTable.setUpdateNote("integration-tests");
        refTable.setConfig(refTableConfig);
        riskClient.populateResonObject(refTable);
        refTableName = refTable.getName();

        RefTableItemArray itemArray = composeRefTableItemArray(1);
        riskClient.populateRefTableItemArray(itemArray);

        context = new HashMap<String,String>();
        context.put("tableName", refTableName);
        context.put("key", itemArray.getItem().get(0).getValues().getValue().get(0));
    }

    @AfterMethod(alwaysRun = true)
    public final void destroyContext() throws Exception {
        if (null != context) {
            context.clear();
            context = null;
        }

        if (null != refTableName) {
            riskClient.deleteResonObject("REFTABLE", refTableName);
        }
    }

    @CaseProperties(
            status = Status.Enable,
            priority = Priority.BVT,
            description = "insert ref-table item -- positive",
            steps = {
                    "1. Create a ref-table with some contents inside",
                    "2. Insert some new item into the ref-table",
                    "2.1 Verify that the new inserted item is available"
            }
    )
    @Test(groups = "GOPFR-952")
    public void insertRefTableItemPositive() throws Exception {
        RefTableItemArray base = RefTableItemArray.class.cast(riskClient.getRefTableItemArray(refTableName, null));

        AccessToken token = NexusConnectApi.instance().getLegacyTokenWithClientSecret(CLIENT_ID, CLIENT_SECRET, SCOPE);
        RefTableItemArray extra = composeRefTableItemArray(1);
        NexusGatewayApi.instance().post(token, NexusGatewayApiEnum.create_risk_reftable_items, context, extra);

        RefTableItemArray total = RefTableItemArray.class.cast(riskClient.getRefTableItemArray(refTableName, null));
        reconcileRefTableArray(total, base, extra);
    }

    @CaseProperties(
            status = Status.Enable,
            priority = Priority.Dailies,
            description = "insert ref-table item -- negative due to insufficient privilege",
            steps = {
                    "1. Create a ref-table with some contents inside",
                    "2. Insert some new item into the ref-table, with improper client/scope",
                    "2.1 Verify that the new inserted item is not available"
            }
    )
    @Test(groups = "GOPFR-952")
    public void insertRefTableItemNegativeInsufficientPrivilege() throws Exception {
        AccessToken token = NexusConnectApi.instance().getLegacyTokenWithClientSecret();
        RefTableItemArray extra = composeRefTableItemArray(1);

        try {
            NexusGatewayApi.instance().post(token, NexusGatewayApiEnum.create_risk_reftable_items, context, extra);
        } catch (Exception e) {
            Assert.assertTrue(e.getMessage().contains("<error>scope_forbidden</error>"), "the error is incorrect");
            return;
        }

        Assert.fail("access is granted to some client without proper scope");
    }

    @CaseProperties(
            status = Status.Enable,
            priority = Priority.Dailies,
            description = "insert ref-table item -- negative",
            steps = {
                    "1. Create a ref-table with some contents inside",
                    "2. Insert some new item into the ref-table, too fat to cache",
                    "2.1 Verify that the new inserted item is not available"
            }
    )
    @Test(groups = "GOPFR-952")
    public void insertRefTableItemNegativeExeedingLimit() throws Exception {
        AccessToken token = NexusConnectApi.instance().getLegacyTokenWithClientSecret(CLIENT_ID, CLIENT_SECRET, SCOPE);
        RefTableItemArray extra = composeRefTableItemArray(MAX_CACHE_SIZE + 1);

        try {
            NexusGatewayApi.instance().post(token, NexusGatewayApiEnum.create_risk_reftable_items, context, extra);
        } catch (Exception e) {
            Assert.assertTrue(e.getMessage().contains("<error code=\"VALIDATION_FAILED\">"), "the error is incorrect");
            Assert.assertTrue(e.getMessage().contains("field=\"sizeLimit\""), "the error is incorrect");
            Assert.assertTrue(e.getMessage().contains("cause=\"Write denied\""), "the error is incorrect");
            return;
        }

        Assert.fail("ref-table items are added unexpectedly when they could exhaust the cache");
    }

    @CaseProperties(
            status = Status.Enable,
            priority = Priority.Comprehensive,
            description = "get ref-table item",
            steps = {
                    "1. Create a ref-table with some contents inside",
                    "1.1 Verify that the items in the ref-table are available"
            }
    )
    @Test(groups = "GOPFR-952")
    public void getRefTableItem() throws Exception {
        RefTableItemArray expected = RefTableItemArray.class.cast(riskClient.getRefTableItemArray(refTableName, null));

        AccessToken token = NexusConnectApi.instance().getLegacyTokenWithClientSecret(CLIENT_ID, CLIENT_SECRET, SCOPE);
        RefTableItemArray actual = TestUtil.parseXml(NexusGatewayApi.instance().get(token, NexusGatewayApiEnum.get_risk_reftable_items, context), RefTableItemArray.class);

        reconcileRefTableArray(actual, expected);
    }

    @CaseProperties(
            status = Status.Manual,
            passed = true,
            priority = Priority.Comprehensive,
            description = "get ref-table item",
            steps = {
                    "1. Create a ref-table with some contents inside",
                    "1.1 Verify that the items in the ref-table are available, when security is enabled"
            }
    )
    @Test(groups = "GOPFR-952")
    public void getRefTableItemWithSecurityEnabled() throws Exception {
        RefTableItemArray expected = RefTableItemArray.class.cast(riskClient.getRefTableItemArray(refTableName, null));

        AccessToken token = NexusConnectApi.instance().getLegacyTokenWithClientSecret(CLIENT_ID, CLIENT_SECRET, SCOPE);

        // com.ea.eadp.risk.service.security.impl.RiskSecurityServiceImpl#X_Header_Simulate_Security_Enable
        // this header actually doesn't take effect, because it's not in the whitelist of nexus proxy
        // so this case is marked as manual as we have to manually change this value in nucleus
        Map<String,String> headers = new HashMap<String,String>();
        headers.put("X_Risk_Security_Simulate_Enable", "true");
        RefTableItemArray actual = TestUtil.parseXml(NexusGatewayApi.instance().get(token, NexusGatewayApiEnum.get_risk_reftable_items, context, headers), RefTableItemArray.class);

        reconcileRefTableArray(actual, expected);
    }

    @CaseProperties(
            status = Status.Enable,
            priority = Priority.Comprehensive,
            description = "get ref-table item",
            steps = {
                    "1. Create a ref-table with some contents inside",
                    "1.1 Verify that the items in the ref-table are available in pages"
            }
    )
    @Test(groups = "GOPFR-952")
    public void getPagedRefTableItem() throws Exception {
        RefTableItemArray firstPage = RefTableItemArray.class.cast(riskClient.getRefTableItemArray(refTableName, null));

        AccessToken token = NexusConnectApi.instance().getLegacyTokenWithClientSecret(CLIENT_ID, CLIENT_SECRET, SCOPE);
        RefTableItemArray secondPage = composeRefTableItemArray(firstPage.getItem().size());
        NexusGatewayApi.instance().post(token, NexusGatewayApiEnum.create_risk_reftable_items, context, secondPage);

        RefTableItemArray total = RefTableItemArray.class.cast(riskClient.getRefTableItemArray(refTableName, null));
        reconcileRefTableArray(total, firstPage, secondPage);

        Map<String,String> headers = new HashMap<String,String>();
        headers.put(PaginationUtils.X_PAGE_SIZE, String.valueOf(firstPage.getItem().size()));
        headers.put(PaginationUtils.X_PAGE_NUMBER, "1"); // replaced by min item id so its presence would be sufficient

        context.put("minItemId", "");
        RefTableItemArray page = TestUtil.parseXml(NexusGatewayApi.instance().get(token, NexusGatewayApiEnum.get_risk_reftable_items_in_page, context, headers), RefTableItemArray.class);
        reconcileRefTableArray(page, firstPage);

        context.put("minItemId", String.valueOf(page.getItem().get(page.getItem().size() - 1).getTableItemId() + 1));
        page = TestUtil.parseXml(NexusGatewayApi.instance().get(token, NexusGatewayApiEnum.get_risk_reftable_items_in_page, context, headers), RefTableItemArray.class);
        reconcileRefTableArray(page, secondPage);
    }

    @CaseProperties(
            status = Status.Enable,
            priority = Priority.Comprehensive,
            description = "udpate ref-table item",
            steps = {
                    "1. Create a ref-table with some contents inside",
                    "2. Update some item inside",
                    "2.1 Verify that the item gets updated correctly"
            }
    )
    @Test(groups = "GOPFR-952")
    public void updateRefTableItem() throws Exception {
        RefTableItemArray expected = RefTableItemArray.class.cast(riskClient.getRefTableItemArray(refTableName, null));
        Assert.assertEquals(expected.getItem().size(), 1, "the init status of ref-table is not correct");
        Assert.assertEquals(expected.getItem().get(0).getValues().getValue().size(), 2, "the init status of ref-table is not correct");

        String newValue = null;

        do {
            newValue = UUID.randomUUID().toString();
        } while (newValue.equals(expected.getItem().get(0).getValues().getValue().get(1)));

        expected.getItem().get(0).getValues().getValue().remove(1);
        expected.getItem().get(0).getValues().getValue().add(newValue);

        AccessToken token = NexusConnectApi.instance().getLegacyTokenWithClientSecret(CLIENT_ID, CLIENT_SECRET, SCOPE);
        NexusGatewayApi.instance().post(token, NexusGatewayApiEnum.create_risk_reftable_items, context, expected);

        RefTableItemArray actual = RefTableItemArray.class.cast(riskClient.getRefTableItemArray(refTableName, null));
        reconcileRefTableArray(actual, expected);
    }

    @CaseProperties(
            status = Status.Enable,
            priority = Priority.Comprehensive,
            description = "delete ref-table item",
            steps = {
                    "1. Create a ref-table with some contents inside",
                    "2. Delete some item inside",
                    "2.1 Verify that the item gets deleted correctly"
            }
    )
    @Test(groups = "GOPFR-952")
    public void deleteRefTableItem() throws Exception {
        RefTableItemArray base = RefTableItemArray.class.cast(riskClient.getRefTableItemArray(refTableName, null));
        Assert.assertEquals(base.getItem().size(), 1, "the init status of ref-table is not correct");
        Assert.assertEquals(base.getItem().get(0).getValues().getValue().size(), 2, "the init status of ref-table is not correct");

        AccessToken token = NexusConnectApi.instance().getLegacyTokenWithClientSecret(CLIENT_ID, CLIENT_SECRET, SCOPE);
        NexusGatewayApi.instance().post(token, NexusGatewayApiEnum.delete_risk_reftable_items, context, base);

        RefTableItemArray actual = RefTableItemArray.class.cast(riskClient.getRefTableItemArray(refTableName, null));
        Assert.assertEquals(actual.getItem().size(), 0, "the ref-table items are not removed");
    }

    RefTableItemArray composeRefTableItemArray(Integer count) throws Exception {
        RefTableItemArray itemArray = new RefTableItemArray();

        for (int i = 0; i < count; i++) {
            Values values = new Values();
            values.getValue().add(UUID.randomUUID().toString());
            values.getValue().add(UUID.randomUUID().toString());

            RefTableItem item = new RefTableItem();
            item.setTableName(refTableName);
            item.setValues(values);

            itemArray.getItem().add(item);
        }

        return itemArray;
    }

    void reconcileRefTableArray(RefTableItemArray after, RefTableItemArray... before) throws Exception {
        Map<String,String> map = new HashMap<String,String>();

        for (RefTableItem item : after.getItem()) {
            Assert.assertEquals(item.getValues().getValue().size(), 2, "the length of some ref-table item is incorrect");
            map.put(item.getValues().getValue().get(0), item.getValues().getValue().get(1));
        }

        for (RefTableItemArray array : before) {
            for (RefTableItem item : array.getItem()) {
                Assert.assertEquals(item.getValues().getValue().size(), 2, "the length of some ref-table item is incorrect");
                Assert.assertEquals(map.remove(item.getValues().getValue().get(0)), item.getValues().getValue().get(1), "some ref-table item is incorrect");
            }
        }

        Assert.assertTrue(map.isEmpty(), "some ref-table item is missing");
    }
}
